﻿#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import logging
import os
import shutil
import sys
import tempfile
import yaml

from oriolusdocker.docker.remove import RemoveCommand
from oriolusdocker.docker.requirements import InstallRequirements, RemoveRequirements, RemoveErrorType
from oriolusdocker.docker.search import SearchCommand
from oriolusdocker.docker.setup import SetupCommand
from oriolusdocker.repos.repository import Repository
from oriolusdocker.scripts.emptyrunner import EmptyRunner
from oriolusdocker.scripts.runner import Runner
from oriolusdocker.storages.filestorage import FileStorage
from oriolusdocker.storages.memorystorage import MemoryStorage
from orioluspith.checker.checker import Checker
from orioluspith.packets.configreader import ConfigReader, ConfigReaderError
from orioluspith.packets.packet import FilePacket

def __logerror__(logger, message):
    if logger.isEnabledFor(logging.DEBUG):
        logger.exception(message)
    else:
        logger.error(message)

class DockerError(Exception):
    def __init__(self, message):
        Exception.__init__(self, message)
        
class DockerFactoryError(Exception):
    def __init__(self, message):
        Exception.__init__(self, message)

class DockerRemoveError(DockerError):
    def __init__(self, message, dependent_packet_full_names):
        DockerError.__init__(self, message)
        self.dependent_packet_full_names = dependent_packet_full_names

class DockerRemoveErrorFactory():
    def __init__(self):
        self.__mapping_table = {RemoveErrorType.NoPacket: self.__get_docker_error,
                                RemoveErrorType.HasChildPackets: self.__get_docker_remove_error}

    def __get_docker_error(self, packet_name, message, data):
        return DockerError("Packet %s can not be removed. Reason: %s" % (packet_name, message))

    def __get_docker_remove_error(self, packet_name, message, data):
        return DockerRemoveError("Packet %s can not be removed. Reason: %s" % (packet_name, message), data)

    def create(self, reason):
        action = self.__mapping_table[reason.type]

        return action(reason.packet_name, reason.message, reason.data)   

class Docker:
    def __init__(self, repository, runner=Runner(), checker = Checker()):
        self.__rep = repository
        self.__runner = runner
        self.__checker = checker
        self.__install_requirements = InstallRequirements()
        self.__remove_requirements = RemoveRequirements()   
        self.__logger = logging.getLogger("Docker")      

    def install(self, packet_path=None):
        if packet_path in [None, ""]:
            message = "Parameter 'packet_path' is not defined"
            __logerror__(self.__logger, message)
            raise DockerError(message)

        if not self.__checker.check_archive_packet(packet_path):
            message = "Packet has invalid format"
            __logerror__(self.__logger, message)
            raise DockerError(message)

        packet = FilePacket(packet_path)
        can_install, reason = self.__install_requirements.check(self.__rep, packet)

        if not can_install:
            message = "Packet %s can not be installed. Reason: %s" % (packet.name, str(reason))
            __logerror__(self.__logger, message)
            raise DockerError(message)

        command = SetupCommand(self.__runner)
        try:
            self.__logger.debug("Packet '%s' will be installed" % packet.name)
            command.execute(packet)
        except Exception, ex:
            message = "Installation failed: %s" % ex.message
            __logerror__(self.__logger, message)
            raise DockerError(message)
        finally:
            self.__rep.update(packet)

    def remove(self, packet_name=None):
        if packet_name in [None, ""]:
            message = "Parameter 'packet_name' is not defined"
            __logerror__(self.__logger, message)
            raise DockerError(message)

        can_remove, reason = self.__remove_requirements.check(self.__rep, packet_name)

        if not can_remove:
            message = "Packet %s can not be removed. Reason: %s" % (packet_name, str(reason.message))
            __logerror__(self.__logger, message)
            raise DockerRemoveErrorFactory().create(reason)

        packet_to_remove = self.__rep.find_packet_by_fullname(packet_name)
        command = RemoveCommand(self.__runner)
        command.execute(packet_to_remove)
        self.__rep.remove(packet_name)

    def search(self, **kwargs):
        command = SearchCommand(self.__rep)

        return command.execute(**kwargs)

    def extract_storage(self):
        return self.__rep.storage

class DockerFactory:
    #Singleton - http://code.activestate.com/recipes/52558/
    class __impl:        
        def __init__(self, reader):
            self.__reader = reader
            self.__location_table_win = {"all_users": "ALLUSERSPROFILE", "user": "USERPROFILE"}
            self.__location_table_nix = {"user": "HOME"}
            self.__logger = logging.getLogger("DockerFactory")
            
        def get_docker(self, config_path = None):
            if not config_path:                                       
                config_path = os.path.join(sys.exec_prefix, "Scripts", "docker_config.yaml")
            if not os.path.exists(config_path):
                message = "Not found file '%s' for docker configuration!" % config_abs_path                 
                __logerror__(self.__logger, message)
                raise DockerFactoryError(message)

            rep = self.__read_config(config_path)

            return Docker(rep)

        def get_virtual_docker(self, docker):
            existing_storage = docker.extract_storage()
            memory_storage = MemoryStorage()
            for packet in existing_storage.entries:
                memory_storage.add(packet)
            virtual_repository = Repository(memory_storage)
            empty_runner = EmptyRunner()

            return Docker(virtual_repository, runner=empty_runner)

        def __read_config(self, filepath):            
            dict = self.__reader.read(filepath)
            self.__logger.debug("Configuration file '%s' will be used for docker" % filepath)
            
            if "repository_folder" in dict.keys():
                folder = dict["repository_folder"]
                self.__logger.debug("repository_folder = %s" % folder)
            if "repository_location" in dict.keys():
                location = dict["repository_location"]
                self.__logger.debug("repository_location = %s" % location)
            if "repository_type" in dict.keys():
                type = dict["repository_type"]
                self.__logger.debug("repository_type = %s" % type)
            
            if sys.platform == "win32":
                location_path = os.environ[self.__location_table_win[location]]                
            else:
                if location == "all_users":
                    message = "Configuration option: %s for platform: %s is not supported" % (location ,sys.platform)
                    __logerror__(self.__logger, message)
                    raise DockerFactoryError (message)                
                location_path =  os.environ[self.__location_table_nix[location]]

            filestorage = FileStorage(os.path.join(location_path, folder))

            return Repository(filestorage)

    __instance = None

    def __init__(self, reader = ConfigReader()):
        if DockerFactory.__instance is None:
            DockerFactory.__instance =  DockerFactory.__impl(reader)

        self.__dict__['_DockerFactory__instance'] = DockerFactory.__instance

    def __getattr__(self, attr):
        return getattr(self.__instance, attr)

    def __setattr__(self, attr, value):
        return setattr(self.__instance, attr, value)